package com.ttx.single.lab.service;

import com.baidu.fsg.uid.UidGenerator;
import com.ttx.single.lab.mapper.OrderItemMapper;
import com.ttx.single.lab.mapper.OrderMapper;
import com.ttx.single.lab.mapper.OrderMappingMapper;
import com.ttx.single.lab.model.dataobject.OrderDO;
import com.ttx.single.lab.model.dataobject.OrderItemDO;
import com.ttx.single.lab.model.dataobject.OrderMapping;
import com.ttx.single.lab.model.dto.PlaceOrderDto;
import com.ttx.single.lab.model.dto.PlaceOrderItemDto;
import com.ttx.single.lab.util.MockUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 订单双写相关的的公共服务
 * @author TimFruit
 * @date 20-4-11 下午12:15
 */
@Component
public class CommonDoubleWriteOrderService {

    @Autowired
    UidGenerator uidGenerator;
    @Autowired
    OrderMappingMapper orderMappingMapper;


    //单库
    @Autowired
    @Qualifier("singleOrderMapper")
    OrderMapper singleOrderMapper;
    @Autowired
    @Qualifier("singleOrderItemMapper")
    OrderItemMapper singleOrderItemMapper;


    //分库分表
    @Autowired
    @Qualifier("shardingOrderMapper")
    OrderMapper shardingOrderMapper;
    @Autowired
    @Qualifier("shardingOrderItemMapper")
    OrderItemMapper shardingOrderItemMapper;


    //=====================================
    // 下单相关
    //=====================================


    public OrderDO generateOrderId(PlaceOrderDto orderDto){
        OrderDO orderDO=new OrderDO();
        BeanUtils.copyProperties(orderDto,orderDO);

        //生成订单唯一id
        Long orderId=uidGenerator.getUID();
        orderDO.setOrderId(orderId);

        Date date=new Date();
        orderDO.setCreateTime(date);
        orderDO.setUpdateTime(date);

        return orderDO;
    }


    public List<OrderItemDO> generateOrderItemIds(List<PlaceOrderItemDto> orderItemDtos, Long orderId){
        if(CollectionUtils.isEmpty(orderItemDtos)){
            return new ArrayList<>();
        }

        List<OrderItemDO> orderItemDOS=new ArrayList<>();
        OrderItemDO itemDO=null;
        Long itemId=null;
        Date date=new Date();
        for(PlaceOrderItemDto placeOrderItemDto: orderItemDtos){
            itemDO=new OrderItemDO();
            BeanUtils.copyProperties(placeOrderItemDto, itemDO);
            itemDO.setOrderId(orderId);


            //生成订单项唯一id
            itemId=uidGenerator.getUID();
            itemDO.setOrderItemId(itemId);

            itemDO.setCreateTime(date);
            itemDO.setUpdateTime(date);
            orderItemDOS.add(itemDO);
        }
        return orderItemDOS;
    }




    //完整下单方法
    public void placeOrder(OrderMapper orderMapper, OrderItemMapper orderItemMapper, PlaceOrderDto orderDto) {
        OrderDO orderDO=this.generateOrderId(orderDto);
        List<OrderItemDO> orderItemDOS=this.generateOrderItemIds(orderDto.getItemDtos(),orderDO.getOrderId());

        this.doPlaceOrder(orderMapper,orderItemMapper,
                orderDO, orderItemDOS);


        //TODO 这里优化，先发送到mq，再插入
        //保存order_id 和 userid的映射关系
        this.insertOrderMapping(orderDO.getOrderId(), orderDO.getUserId());
    }


    //简化后可重用的下单方法
    public void doPlaceOrder(OrderMapper orderMapper, OrderItemMapper itemMapper,
                             OrderDO orderDO, List<OrderItemDO> orderItemDOS) {
        //使用可插入自定义id的方法
        orderMapper.insertWidthId(orderDO);
        Assert.state(!CollectionUtils.isEmpty(orderItemDOS), "订单项个数必须大于0");
        for(OrderItemDO itemDO: orderItemDOS){//为方便示例，所以循环插入
            itemMapper.insertWidthId(itemDO);
        }
    }


    public void insertOrderMapping(Long orderId,Integer userId){
        //TODO 这里优化，先发送到mq，再插入
        OrderMapping mapping=new OrderMapping();
        mapping.setOrderId(orderId);
        mapping.setUserId(userId);

        Date date=new Date();
        mapping.setUpdateTime(date);
        mapping.setCreateTime(date);

        orderMappingMapper.insert(mapping);
    }




    //注意，如果这里在调用方写(如在OrderServiceTransformStep2写)，是不会生效的
    // @Transactional(propagation = Propagation.REQUIRES_NEW)需要在被调用方(非调用方)写
    //这里起新的事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void singlePlaceOrder(OrderDO orderDO, List<OrderItemDO> orderItemDOS){
        this.doPlaceOrder(singleOrderMapper,singleOrderItemMapper,
                orderDO, orderItemDOS);
        //模拟异常
        MockUtil.randomException();
    }

    //这里使用分库事务管理器起新的事务
    @Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = "shardingTransactionManager")
    public void shardingPlaceOrder(OrderDO orderDO, List<OrderItemDO> orderItemDOS){
        this.doPlaceOrder(shardingOrderMapper,shardingOrderItemMapper,
                orderDO, orderItemDOS);


        //模拟异常
        MockUtil.randomException();
    }




    //=====================================
    // 取消订单
    //=====================================

    //由程序统一设置更新时间，因为如果set update_time=#{updateTime}的话， 由于调用延时，会导致双写更新时间有点不一样，如延迟1s
    public void cancelOrder(OrderMapper orderMapper,Long orderId, Integer userId,Date updateTime) {
        Assert.notNull(userId,"[userId]不能为空");
        Assert.notNull(orderId,"[orderId]不能为空");


        orderMapper.updateOrderStatus(orderId, userId, "cancel",updateTime);

    }


    //这里起新的事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void singleCancelOrder(Long orderId, Integer userId,Date updateTime){
        this.cancelOrder(singleOrderMapper, orderId, userId, updateTime);
        //模拟异常
        MockUtil.randomException();
    }

    //这里使用分库事务管理器起新的事务
    @Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = "shardingTransactionManager")
    public void shardingCancelOrder(Long orderId, Integer userId,Date updateTime){
        this.cancelOrder(shardingOrderMapper, orderId, userId, updateTime);
        //模拟异常
        MockUtil.randomException();
    }

}
